package helper.scxml.scxml2.zone.t2

import helper.base.A2LHM
import helper.base.LHMHelper.A2LHMExpand.add
import helper.base.LHMHelper.A2LHMExpand.makeA2LHM
import helper.base.LHMHelper.A2LHMExpand.toStr
import helper.base.LHMHelper.A2LHMExpand.touch
import helper.base.PrintHelper.toPrettyString
import helper.base.ZoneConstraintHelper.Constraint
import helper.base.ZoneConstraintHelper.PointConstraint
import helper.base.ZoneConstraintHelper.PointConstraint.Companion.makePointConstraint
import helper.base.debug.DebugHelper.DebuggerList
import helper.base.debug.DebugRes.d_getInitialZoneState
import helper.base.debug.DebugRes.d_plnInitialG
import helper.base.math.TreeHelper.T2HierarchyStructure
import helper.scxml.scxml2.EnvHelper.RunResult
import helper.scxml.scxml2.EnvHelper.StateEventDataUnit.Companion.makeStateEventDataUnit
import helper.scxml.scxml2.zone.t2.ConfigHelper.Config
import helper.scxml.scxml2.zone.t2.ConfigHelper.Config.Companion.makeConfig
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ConstraintGetter
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ConstraintGetter.Companion.makeConstraintGetter
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ConstraintGetterArg.Companion.makeConstraintGetterArg
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ZoneEnv
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ZoneEnv.Companion.tClock
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.equal_name_StrStrBoolFun
import helper.scxml.strategy.StrategyHelper.TimedEventHelper.TimedEvent

object ScxmlEnvHelper {
    abstract class ScxmlEnv : ZoneEnv() {
        abstract val aStateH: T2HierarchyStructure<String>
        abstract val aStateI: A2LHM<String?, LinkedHashSet<String>>
        abstract val aStateF: A2LHM<String, LinkedHashSet<String>>

        open fun printlnHIF() {
            println("-".repeat(60))
            println(aStateH)
            println("-".repeat(60))
            println(aStateI)
            println("-".repeat(60))
            println(aStateF)
            println("-".repeat(60))
            println("aStateEventStateLHM:")
            println(aStateEventStateLHM.toStr())
            println("-".repeat(60))
            println("locationStayMaxLHM:")
            println(stateStayMaxLHM)
            println("-".repeat(60))
            println("aLocationEnterConstraintLHM:")
            aStateEnterConstraintLHM.touch { a1, a2 ->
                println(a1)
                println("\t${a2}")
            }
            println("-".repeat(60))
            println("allVars:")
            println(allVars)
            println("-".repeat(60))
            println(aStringEToDoubleLHM.toPrettyString())
            println("-".repeat(60))
            println("eventActionLHM:")
            println(eventActionLHM.toPrettyString())
        }

        override fun isOnFinalLocation(nowZS: Config): Boolean {
            return aStateH.findChildren(null)!!.all { locationLevel ->
                aStateF[locationLevel]!!.any { finalLocation ->
                    nowZS.states.contains(finalLocation)
                }
            }
        }

        override fun isEnd(nowZS: Config, nextMinEventTime: Int): Boolean {
            require(nextMinEventTime >= 0)
            val nowT = nowZS.byNameFindC(tClock).getPointD()
            return isOnFinalLocation(nowZS) || nowT + nextMinEventTime > timeNeedEnd
        }

        val tConstraintGetter: ConstraintGetter = makeConstraintGetter(
            tClock,
            equal_name_StrStrBoolFun,
        ) { (oldZS, oldConstraint, timeEventChoiceArr) ->
            require(oldConstraint is PointConstraint)
            val time = timeEventChoiceArr[0].time
            val d1 = oldConstraint.getPointD() + time
            makePointConstraint(oldConstraint.variable, d1)
        }

        open val aWithoutClocksTheVars: List<String> = listOf()

        override fun updateConstraint(
            exitStateList: ArrayList<String>,
            oldConfig: Config,
            newConfig: Config,
            timedEventList: List<TimedEvent>,
        ) {
            var theOldConfig = oldConfig
            tConstraintGetter.let {
                val newC: Constraint = it.getNewConstraint(
                    makeConstraintGetterArg(
                        theOldConfig.states,
                        newConfig,
                        timedEventList,
                        newConfig.states,
                    )
                )
                newConfig.varConstraintLHM[newC.getVariablesString()] = newC
            }
            theOldConfig = newConfig.clone()
            val enterStateList = arrayListOf<String>()
//            println("exitStateList=${exitStateList}")
            exitStateList.forEach { exitState ->
                stateExitActionLHM.doActions(exitState, theOldConfig, newConfig)
                theOldConfig = newConfig.clone()
                val aEventEnterStateLHM = aStateEventStateLHM[exitState]
                aEventEnterStateLHM?.forEach { (event, enterState) ->
                    if (timedEventList.map { it.event }.contains(event)) {
                        eventActionLHM.doActions(event, theOldConfig, newConfig)
                        theOldConfig = newConfig.clone()
                        enterStateList.add(enterState)
                    }
                }
            }
            enterStateList.forEach { enterState ->
                stateEnterActionLHM.doActions(enterState, theOldConfig, newConfig)
                theOldConfig = newConfig.clone()
            }
//            println("updateConstraint end")
        }

        override fun getInitialConfig(debuggerList: DebuggerList): Config {
            debuggerList.run {
                d(
                    d_getInitialZoneState,
                ) {
                    println("fun getInitialZoneState")
                    println("aStateI:${aStateI.toStr()}")
                }
            }
            val locations: ArrayList<String> = ArrayList()
            aStateI[null]!!.forEach {
                locations.add(aStateI[it]!!.first())
            }
            val constraints = arrayListOf<Constraint>()
            allVars.forEach {
                constraints.add(makePointConstraint(it, 0.0))
            }
            val config = makeConfig(
                states = locations,
                zcs = constraints,
            )
            debuggerList.run {
                d(d_plnInitialG) {
                    println("d_plnInitialG:")
                    println("aStateH:")
                    config.getT1ParentChildRelations(
                        aStateH
                    ).let {
                        println(it)
                    }
                    println("config:")
                    println(config)
                    println(":d_plnInitialG")
                }
            }
            return config
        }
    }

    val env_tvsNoteRunResultFun_fun: (ScxmlEnv) -> NoteRunResultFun = { scxmlEnv: ScxmlEnv ->
        { state: String, nowConfig: Config, timedEvent: TimedEvent, runResult: RunResult ->
            runResult.us.add(
                makeStateEventDataUnit(
                    state,
                    timedEvent.event,
                    makeA2LHM<String, String>().also { dataLHM ->
                        dataLHM.add(
                            tClock, "${nowConfig.byNameFindC(tClock).getPointD() + timedEvent.time}"
                        )
                        scxmlEnv.aWithoutClocksTheVars.forEach {
                            dataLHM.add(it, "${nowConfig.byNameFindC(it).getPointD()}")
                        }
                    },
                )
            )
        }
    }
}